#include <windows.h>
#include "lib.h"

#include "SicBoGame.h"
#include "GameDBInterface.h"
#include "SicBoSQL.h"
#include "SicBoPayTables.h"
#include "getrand.h"
#include "crapconn.h"
#include "opts.h"

#include "log.h"

#include "badpacketmsgs.h"


///////////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     SicBoGame::SicBoGame(CrapsConnection *pConn)
///
/// Constructor
///
/// @param  pConn  -    represents the connection to the player and, to a lesser extent, the database
///
///////////////////////////////////////////////////////////////////////////////////////////////////////
SicBoGame::SicBoGame(CrapsConnection *pConn)
{
    m_pConn      = pConn;    
    m_nBalance   = 0;

    // Compute the Maximum Bet for each bet type.  
    // This is the maximum table bet divided by the specific bet's payout.
    SicBoOptions *pOptions = ((SicBoSQL *) m_pConn->getGameDBInterface())->getOptions();

    m_ucPayTable = pOptions->ucPayTable;

    for(int nI=0; nI<SicBoBet::MAX_BET_TYPE; ++nI)
    {       
        if(nI<SicBoBet::SINGLE_1 || nI>SicBoBet::SINGLE_6)
        {
            m_nBetMax[nI] = pOptions->nMaxLimit/arrPayTable[m_ucPayTable][nI];
        }
        else
        {
            m_nBetMax[nI] = pOptions->nMaxLimit/arrAuxPayTable[m_ucPayTable][2];
        }
    }    
}


///////////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     SicBoGame::~SicBoGame()
///
/// Destructor
///
///////////////////////////////////////////////////////////////////////////////////////////////////////
SicBoGame::~SicBoGame()
{

}

///////////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     SicBoGame::handleMessage(unsigned char *pucMsgBuf, int32 nMsgLen)
///
/// This gets called by the connection object for all message types not specifically handled 
/// by the connection.  
///
/// @param  pucMsgBuf   -   (in) The raw message data
/// @param  nMsgLen     -   (in) the length of the message data
///
/// @return bool8   true if the message was handled, false if not.
///
///////////////////////////////////////////////////////////////////////////////////////////////////////
bool8 SicBoGame::handleMessage(unsigned char *pucMsgBuf, int32 nMsgLen)
{
    bool8 bRetVal = true;

    switch (pucMsgBuf[0])
    {
        case 'R':
        {
            bRetVal = processRollMessage(pucMsgBuf, nMsgLen);

            break;
        }
    }

    return bRetVal;
}


///////////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     SicBoGame::processRollMessage(uint8 *pucMsgBuf, int32 nMsgLen)
///
/// This gets called for "Roll" messages from the client and handles all of the details of generating
/// the roll, updating the database, and sending a response back to the client.
///
/// @param  pucMsgBuf   -   (in) The raw "Roll" message data
/// @param  nMsgLen     -   (in) the length of the message data
///
/// @return bool8   true if the roll message was successfully handled, false if not.
///
///////////////////////////////////////////////////////////////////////////////////////////////////////
bool8 SicBoGame::processRollMessage(uint8 *pucMsgBuf, int32 nMsgLen)
{
    bool8 bRetVal = true;

    try
    {
        // all valid Roll messages will be at least 7 bytes long.
        if(nMsgLen<7)
        {
            Log::msgError("SicBo Roll: invalid roll message format  ");

            m_pConn->trackaddBadPacket(INVALID_NUMBER_BETS,pucMsgBuf[0],(char*)pucMsgBuf,nMsgLen);

            m_pConn->msgStart();
            m_pConn->msgAdd((uint8)'B');
            m_pConn->msgEnd();
        
            bRetVal = false;
            throw new Exception;
        }

        uint8 ucNumBets = 0;
        int32 nOffset  = 1;

        // get the number of different bets that the player made
        nOffset += m_pConn->msgRemove(pucMsgBuf + nOffset, &ucNumBets);

        int32 nNumBets = ucNumBets;
        
        // must have at least 1 and no more than MAX_BET_TYPE.  Also, make sure that the message
        // length is right for the specified number of bets.
        if(nNumBets<=0 || nNumBets>SicBoBet::MAX_BET_TYPE || ((nNumBets * 5) + 2)>nMsgLen)
        {
            Log::msgError("SicBo Roll: invalid number of bets  ");

            m_pConn->trackaddBadPacket(INVALID_NUMBER_BETS,pucMsgBuf[0],(char*)pucMsgBuf,nMsgLen);

            m_pConn->msgStart();
            m_pConn->msgAdd((uint8)'B');
            m_pConn->msgEnd();
        
            return false;
        }

        // clear out any old bet info from previous rolls.
        memset(m_arrBets,0,sizeof(m_arrBets));

        SicBoSQL *pSQL = (SicBoSQL *) m_pConn->getGameDBInterface();
        SicBoOptions *pOptions = pSQL->getOptions();
        
        int32 nTotalBet = 0;

        // okay now read in the actual bet ids and bet amounts that the player made.
        for(int nI=0; nI<nNumBets; ++nI)
        {
            // bet id
            uint8 ucBetType;
            nOffset += m_pConn->msgRemove(pucMsgBuf + nOffset, &ucBetType);

            // bet amount
            int32 nBet = 0;
            nOffset += m_pConn->msgRemove(pucMsgBuf + nOffset, &nBet);

            // verify that the bet id is valid, that there's a maximum of 1 of each bet type, and that
            // the bet isn't larger than the maximum bet for this bet type.
			// cgc 20090914: new check, bet must be more tan 0 (no negative or 0 bets). 
            if(ucBetType >= SicBoBet::MAX_BET_TYPE || m_arrBets[ucBetType].nBetAmt!=0 || 
               nBet > m_nBetMax[ucBetType] || (nBet <= 0))
            {
                Log::msgError("SicBo Roll: invalid bet  ");

                m_pConn->trackaddBadPacket(INVALID_NUMBER_BETS,pucMsgBuf[0],(char*)pucMsgBuf,nMsgLen);

                m_pConn->msgStart();
                m_pConn->msgAdd((uint8)'B');
                m_pConn->msgEnd();
            
                bRetVal = false;
                throw new Exception;
            }            

            // okay, it all checked out, accept this bet.
            m_arrBets[ucBetType].nBetAmt = nBet;
            nTotalBet += m_arrBets[ucBetType].nBetAmt;
        }

        // Make sure that the total bet amount is within the table limits
        if(nTotalBet<pOptions->nMinLimit || nTotalBet>pOptions->nMaxLimit)
        {
            Log::msgError("SicBo Roll: Bet value outside defined table limits  ");

            m_pConn->trackaddBadPacket(INVALID_NUMBER_BETS, pucMsgBuf[0], (char*) pucMsgBuf, nMsgLen);

            m_pConn->msgStart();
            m_pConn->msgAdd((uint8)'B');
            m_pConn->msgEnd();

            bRetVal = false;
            throw new Exception;
        }

        // generate the dice values
		uint8 ucDie1, ucDie2, ucDie3;
		if(ServerOpts::bAllowTesting && ServerOpts::IsDebugCasinoName() && m_pConn->isForcedRoll())
		{
			ucDie1 = m_pConn->getForcedRoll();
			ucDie2 = m_pConn->getForcedRoll();
			ucDie3 = m_pConn->getForcedRoll();
		}
		else
		{
			ucDie1 = (uint8)((getrand() % 6) + 1);
			ucDie2 = (uint8)((getrand() % 6) + 1);
			ucDie3 = (uint8)((getrand() % 6) + 1);
		}

#ifdef DEBUG_KNOWNROLLS
        // Testing logic.  Loads in spins from debug table.
        pSQL->getDebugRoll(&ucDie1, &ucDie2, &ucDie3);
#endif


        DEBUGMSG(("\n\nDice   %d   %d   %d",(int) ucDie1, (int) ucDie2, (int) ucDie3));

        // evaluate the dice and the bets to get the total amount won or lost.
        int32 nBalAdj = 0;
        int32 nTotalWinAmt = 0;
        int32 nWinCount = calcPayout(ucDie1, ucDie2, ucDie3, nNumBets, nBalAdj, nTotalWinAmt);        

        // prepare messsage back to client
        m_pConn->msgStart();

        // commit the roll results to the database and get the new player balance
        if(pSQL->trackRoll(ucDie1, ucDie2, ucDie3, m_arrBets, nNumBets, nTotalBet, nTotalWinAmt, nBalAdj, &m_nBalance))
        {            
            DEBUGMSG(("New Balance:  %d\tTotal Bet:  %d\tWin:  %d\tBalAdj:  %d\n", m_nBalance, nTotalBet, nTotalWinAmt, nBalAdj));

            m_pConn->msgAdd((uint8)'R');
            m_pConn->msgAdd(ucDie1);
            m_pConn->msgAdd(ucDie2);
            m_pConn->msgAdd(ucDie3);
            m_pConn->msgAdd(nTotalWinAmt);  // this is just the amount won, without subtracting out lost bets or adding in returned bets
            m_pConn->msgAdd(m_nBalance);
            m_pConn->msgAdd(nWinCount);
            
            if(nWinCount>0)
            {
                for(int32 nI=0;nI<SicBoBet::MAX_BET_TYPE;++nI)
                {
                    if(m_arrBets[nI].nWinAmt>0)
                    {
                        m_pConn->msgAdd((uint8) nI);
                        m_pConn->msgAdd(m_arrBets[nI].nWinAmt);
                    }
                }
            }
        }
        else if(nTotalBet>m_nBalance)
        {
            m_pConn->msgAdd((uint8)'$');
        }
        else
        {
            Log::msgError("ERROR...\n");
            m_pConn->msgAdd((uint8)'N');
        }

        m_pConn->msgEnd();

    }
    catch(Exception *pExc)
    {
        delete pExc;
    }
   
    return bRetVal;
}



///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn   SicBoGame::calcPayout(uint8 ucDie1, uint8 ucDie2, uint8 ucDie3, int32 nNumBets, int32& nBalAdj, int32& nTotalWinAmt)
///
/// This method takes the dice values and checks whether any of the player's bets should pay off, and if so how much.
///
/// @param  ucDie1      -   (in)  the value of the first die
/// @param  ucDie2      -   (in)  the value of the second die
/// @param  ucDie3      -   (in)  the value of the third die
/// @param  nNumBets    -   (in)  the number of bets the player made
/// @param  nBalAdj     -   (out) the total amount that the player wins, including returned bets
/// @param  nTotalWinAmt-   (out) the amount the player wins, NOT including returned bets.
///
/// @return int32   the number of bets the player won on
///
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int32 SicBoGame::calcPayout(uint8 ucDie1, uint8 ucDie2, uint8 ucDie3, int32 nNumBets, int32& nBalAdj, int32& nTotalWinAmt)
{
    int32 nWinCount = 0;    

    nTotalWinAmt    = 0;
    nBalAdj         = 0;

    int32 nSum = ucDie1 + ucDie2 + ucDie3;

    // go through the entire bet array, until we've accounted for all of the bets the player made.
    for(int nI=0; nI<SicBoBet::MAX_BET_TYPE && nWinCount<nNumBets; ++nI)
    {       
        if(m_arrBets[nI].nBetAmt>0) // if this is a bet the player placed money on
        {
            // handle the bet appropriately
            switch((SicBoBet::BetTypes) nI)
            {
                case SicBoBet::SMALL:
                {
                    // if it's not a triple and the sum of the dice is between 4 and 10 (inclusive)
                    if( (ucDie1==ucDie2 && ucDie2==ucDie3)==false && (nSum>3 && nSum<11))
                    {
                        // Win!
                        m_arrBets[nI].nWinAmt = m_arrBets[nI].nBetAmt*arrPayTable[m_ucPayTable][nI];
                        ++nWinCount;

                        nTotalWinAmt += m_arrBets[nI].nWinAmt;
                        nBalAdj += m_arrBets[nI].nBetAmt + m_arrBets[nI].nWinAmt;

                        DEBUGMSG(("\tWon SMALL.  Bet:  %d  Win:  %d", m_arrBets[nI].nBetAmt, m_arrBets[nI].nWinAmt));
                    }

                    break;
                }
                case SicBoBet::BIG:
                {
                    // if it's not a triple and the sum of the dice is between 11 and 17 (inclusive)
                    if( (ucDie1==ucDie2 && ucDie2==ucDie3)==false && (nSum>=11 && nSum<18))
                    {
                        // WIN!
                        m_arrBets[nI].nWinAmt = m_arrBets[nI].nBetAmt*arrPayTable[m_ucPayTable][nI];
                        ++nWinCount;

                        nTotalWinAmt += m_arrBets[nI].nWinAmt;
                        nBalAdj += m_arrBets[nI].nBetAmt + m_arrBets[nI].nWinAmt;

                        DEBUGMSG(("\tWon BIG.  Bet:  %d  Win:  %d", m_arrBets[nI].nBetAmt, m_arrBets[nI].nWinAmt));
                    }

                    break;
                }
                case SicBoBet::TOTAL_4:
                case SicBoBet::TOTAL_5:
                case SicBoBet::TOTAL_6:
                case SicBoBet::TOTAL_7:
                case SicBoBet::TOTAL_8:
                case SicBoBet::TOTAL_9:
                case SicBoBet::TOTAL_10:
                case SicBoBet::TOTAL_11:
                case SicBoBet::TOTAL_12:
                case SicBoBet::TOTAL_13:
                case SicBoBet::TOTAL_14:
                case SicBoBet::TOTAL_15:
                case SicBoBet::TOTAL_16:
                case SicBoBet::TOTAL_17:
                {          
                    // does the sum equal the total that the player selected?
                    if(nSum == (nI-SicBoBet::TOTAL_4+4))
                    {
                        // WIN!
                        m_arrBets[nI].nWinAmt = m_arrBets[nI].nBetAmt*arrPayTable[m_ucPayTable][nI];
                        ++nWinCount;

                        nTotalWinAmt += m_arrBets[nI].nWinAmt;
                        nBalAdj += m_arrBets[nI].nBetAmt + m_arrBets[nI].nWinAmt;

                        DEBUGMSG(("\tWon TOTAL %d.  Bet:  %d  Win:  %d", nSum, m_arrBets[nI].nBetAmt, m_arrBets[nI].nWinAmt));
                    }

                    break;
                }

                case SicBoBet::SINGLE_1:
                case SicBoBet::SINGLE_2:
                case SicBoBet::SINGLE_3:
                case SicBoBet::SINGLE_4:
                case SicBoBet::SINGLE_5:
                case SicBoBet::SINGLE_6:
                {
                    // the player bet that one or more dice would show the selected value
                    int32 nValue = nI - SicBoBet::SINGLE_1 + 1;

                    int32 nMatches = 0;

                    if(nValue == ucDie1)
                    {
                        ++nMatches;
                    }

                    if(nValue == ucDie2)
                    {
                        ++nMatches;
                    }

                    if(nValue == ucDie3)
                    {
                        ++nMatches;
                    }

                    if(nMatches>0)
                    {
                        // Win!  Do a lookup on the payout according to how many dice came up with the selected value
                        m_arrBets[nI].nWinAmt = m_arrBets[nI].nBetAmt*arrAuxPayTable[m_ucPayTable][nMatches-1];                        
                        ++nWinCount;

                        nTotalWinAmt += m_arrBets[nI].nWinAmt;
                        nBalAdj += m_arrBets[nI].nBetAmt + m_arrBets[nI].nWinAmt;

                        DEBUGMSG(("\tWon SINGLE %d.  Bet:  %d  Win:  %d", nValue, m_arrBets[nI].nBetAmt, m_arrBets[nI].nWinAmt));
                    }

                    break;
                }

                case SicBoBet::DOUBLE_1:
                case SicBoBet::DOUBLE_2:
                case SicBoBet::DOUBLE_3:
                case SicBoBet::DOUBLE_4:
                case SicBoBet::DOUBLE_5:
                case SicBoBet::DOUBLE_6:
                {
                    // which face value did the user bet would come up on two dice?
                    int32 nValue = nI - SicBoBet::DOUBLE_1 + 1;

                    // do at least two of our dice match this value?
                    int nMatch = 0;

                    if(ucDie1==nValue)
                    {
                        ++nMatch;
                    }

                    if(ucDie2==nValue)
                    {
                        ++nMatch;
                    }

                    if(ucDie3==nValue)
                    {
                        ++nMatch;
                    }

                    if(nMatch>1)
                    {
                        // WIN!
                        m_arrBets[nI].nWinAmt = m_arrBets[nI].nBetAmt*arrPayTable[m_ucPayTable][nI];                        
                        ++nWinCount;

                        nTotalWinAmt += m_arrBets[nI].nWinAmt;
                        nBalAdj += m_arrBets[nI].nBetAmt + m_arrBets[nI].nWinAmt;

                        DEBUGMSG(("\tWon DOUBLE %d.  Bet:  %d  Win:  %d", nValue, m_arrBets[nI].nBetAmt, m_arrBets[nI].nWinAmt));
                    }

                    break;
                }

                case SicBoBet::TRIPLE_1:
                case SicBoBet::TRIPLE_2:
                case SicBoBet::TRIPLE_3:
                case SicBoBet::TRIPLE_4:
                case SicBoBet::TRIPLE_5:
                case SicBoBet::TRIPLE_6:
                {
                    // what face value was selected by the user?
                    int32 nValue = nI - SicBoBet::TRIPLE_1 + 1;

                    // did this value come up on all 3 dice?
                    if(ucDie1==nValue && ucDie2==nValue && ucDie3==nValue)
                    {
                        // WIN!
                        m_arrBets[nI].nWinAmt = m_arrBets[nI].nBetAmt*arrPayTable[m_ucPayTable][nI];
                        ++nWinCount;

                        nTotalWinAmt += m_arrBets[nI].nWinAmt;
                        nBalAdj += m_arrBets[nI].nBetAmt + m_arrBets[nI].nWinAmt;

                        DEBUGMSG(("\tWon TRIPLE %d.  Bet:  %d  Win:  %d", nValue, m_arrBets[nI].nBetAmt, m_arrBets[nI].nWinAmt));
                    }

                    break;
                }

                case SicBoBet::TRIPLE_ANY:
                {
                    // any triple will do...did we get it?
                    if(ucDie1==ucDie2 && ucDie1==ucDie3)
                    {
                        // WIN!
                        m_arrBets[nI].nWinAmt = m_arrBets[nI].nBetAmt*arrPayTable[m_ucPayTable][nI];
                        ++nWinCount;

                        nTotalWinAmt += m_arrBets[nI].nWinAmt;
                        nBalAdj += m_arrBets[nI].nBetAmt + m_arrBets[nI].nWinAmt;

                        DEBUGMSG(("\tWon TRIPLE ANY.  Bet:  %d  Win:  %d", m_arrBets[nI].nBetAmt, m_arrBets[nI].nWinAmt));
                    }

                    break;
                }

                case SicBoBet::COMBO_1_2:
                case SicBoBet::COMBO_1_3:
                case SicBoBet::COMBO_1_4:
                case SicBoBet::COMBO_1_5:
                case SicBoBet::COMBO_1_6:
                case SicBoBet::COMBO_2_3:
                case SicBoBet::COMBO_2_4:
                case SicBoBet::COMBO_2_5:
                case SicBoBet::COMBO_2_6:
                case SicBoBet::COMBO_3_4:
                case SicBoBet::COMBO_3_5:
                case SicBoBet::COMBO_3_6:
                case SicBoBet::COMBO_4_5:
                case SicBoBet::COMBO_4_6:
                case SicBoBet::COMBO_5_6:
                {
                    // which 2 dice combo did the player bet on?
                    int32 nNdx = nI - SicBoBet::COMBO_1_2;

                    if((ucDie1==arrCombinations[nNdx][0] ||
                        ucDie2==arrCombinations[nNdx][0] ||
                        ucDie3==arrCombinations[nNdx][0] ) &&
                       (ucDie1==arrCombinations[nNdx][1] ||
                        ucDie2==arrCombinations[nNdx][1] ||
                        ucDie3==arrCombinations[nNdx][1] ))
                    {
                        // WIN!
                        m_arrBets[nI].nWinAmt = m_arrBets[nI].nBetAmt*arrPayTable[m_ucPayTable][nI];
                        ++nWinCount;

                        nTotalWinAmt += m_arrBets[nI].nWinAmt;
                        nBalAdj += m_arrBets[nI].nBetAmt + m_arrBets[nI].nWinAmt;

                        DEBUGMSG(("\tWon COMBO  %d.  Bet:  %d  Win:  %d", nNdx, m_arrBets[nI].nBetAmt, m_arrBets[nI].nWinAmt));
                    }

                    break;
                }
            }
        }
    }

    // how many did the player win in all?
    return nWinCount;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     SicBoGame::send(bool8 bSaved)
///
/// Called when the player initially connects.  Here we send back the limit and active pay table
/// to the client.
///
///////////////////////////////////////////////////////////////////////////////////////////////////
void SicBoGame::send(bool8)
{
    SicBoOptions *pOptions = ((SicBoSQL *) m_pConn->getGameDBInterface())->getOptions();

    m_pConn->msgAdd(pOptions->nMinLimit);
    m_pConn->msgAdd(pOptions->nMaxLimit);
    m_pConn->msgAdd(m_ucPayTable);
}


///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     SicBoGame::save()
///
/// Pure virtual function from base class.  Not needed for us though, as sicbo has no saved games.
///
///////////////////////////////////////////////////////////////////////////////////////////////////
void SicBoGame::save()
{

}

